package com.basker.pisces.domain.entity.meta;

import java.lang.reflect.Array;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;

import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.ResolvableType;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import com.basker.pisces.core.meta.ICollectionField;
import com.basker.pisces.core.meta.IDataField;
import com.basker.pisces.core.meta.IDataObjectMeta;
import com.basker.pisces.core.meta.bean.BeanMetaManager;
import com.basker.pisces.core.query.meta.QueryMeta;
import com.basker.pisces.core.query.meta.bean.BeanQueryMeta;
import com.basker.pisces.core.repository.IDataRepository;
import com.basker.pisces.domain.command.CommandResult;
import com.basker.pisces.domain.command.meta.Command;
import com.basker.pisces.domain.command.meta.DeleteCommand;
import com.basker.pisces.domain.command.meta.SaveCommand;
import com.basker.pisces.domain.command.option.CommandExecuteOption;
import com.basker.pisces.domain.command.option.SaveCommandExecuteOption;
import com.basker.pisces.domain.command.run.CommandExecutor;
import com.basker.pisces.domain.constant.DomainConstants;
import com.basker.pisces.domain.entity.annotation.EntityMeta;
import com.basker.pisces.domain.entity.data.BeanDataEntity;
import com.basker.pisces.domain.entity.data.DefaultRowListDataEntity;
import com.basker.pisces.domain.entity.data.IRowListDataEntity;
import com.basker.pisces.domain.property.meta.Property;
import com.basker.pisces.domain.property.meta.ReferenceProperty;
import com.basker.pisces.domain.validation.ValidationErrors;
import com.basker.pisces.domain.validation.meta.Validation;
import com.basker.pisces.spring.SpringApplicationContext;

public abstract class BeanEntity<TBeanType> extends Entity implements InitializingBean {

    private transient final BeanEntityAnnotationResolver annotationResolver = new BeanEntityAnnotationResolver(this);
    @Autowired
    protected transient IDataRepository repository;
    private transient Class<TBeanType> beanType;
    @Autowired
    private transient CommandExecutor commandExecutor;

    protected BeanEntity() {

    }

    @Override
    public final void afterPropertiesSet() throws Exception {
        this.setBeanType(this.findBeanType());

        this.registerProperties();
        this.registerValidations();
        this.registerCommands();

        this.build();

        // 初始化一下
        this.getDataObjectMeta();

        this.sort();
    }

    public CommandExecuteOption createCommandExecuteOption(Command command) {
        return new CommandExecuteOption();
    }

    @Override
    public BeanDataEntity<TBeanType> createDataEntity() {
        return new BeanDataEntity<TBeanType>(this, repository);
    }

    @Override
    public IRowListDataEntity createRowListDataEntity() {
        return new DefaultRowListDataEntity(this, repository);
    }

    /**
     * {@link BeanEntity}提供一个内置的删除命令，该方法即执行这个内置命令
     *
     * @param pkValues
     * @return
     * @see #getDeleteCommand()
     */
    public final CommandResult delete(List<Object> pkValues) {
        return this.delete(pkValues, null);
    }

    /**
     * {@link BeanEntity}提供一个内置的删除命令，该方法即执行这个内置命令，传入option对象控制命令执行过程内部的一些逻辑
     *
     * @param pkValues
     * @param option
     * @return
     * @see #getDeleteCommand()
     */
    public final CommandResult delete(List<Object> pkValues, CommandExecuteOption option) {
        return this.executeCommand(this.getDeleteCommand(), pkValues, option);
    }

    /**
     * {@link BeanEntity}提供一个内置的删除命令，该方法即执行这个内置命令
     *
     * @param pkValue
     * @return
     * @see #getDeleteCommand()
     */
    public final CommandResult delete(Object pkValue) {
        return this.delete(Arrays.asList(pkValue), null);
    }

    /**
     * 执行传入的命令，业务对象传入主键值
     *
     * @param command
     * @param pkValues
     * @return
     */
    public final CommandResult executeCommand(Command command, List<Object> pkValues) {
        return this.getCommandExecutor().execute(this, command, pkValues);
    }

    /**
     * 执行传入的命令，业务对象传入主键值，并传入option对象控制命令执行过程内部的一些逻辑
     *
     * @param command
     * @param pkValues
     * @param option
     * @return
     */
    public final CommandResult executeCommand(Command command, List<Object> pkValues, CommandExecuteOption option) {
        return this.getCommandExecutor().execute(this, command, pkValues, option);
    }

    /**
     * 执行传入的命令，可以传入业务对象（多个业务对象传入数组）、也可以是主键（多个主键传入List）
     *
     * @param command
     * @param pkValueOrDataObject
     * @return
     */
    public final CommandResult executeCommand(Command command, Object pkValueOrDataObject) {
        return this.executeCommand(command, pkValueOrDataObject, null);
    }

    /**
     * 执行传入的命令，可以传入业务对象（多个业务对象传入数组）、也可以是主键（多个主键传入List），并传入option对象控制命令执行过程内部的一些逻辑
     *
     * @param command
     * @param pkValueOrDataObject
     * @param option
     * @return
     */
    public final CommandResult executeCommand(Command command, Object pkValueOrDataObject,
                                              CommandExecuteOption option) {
        Assert.notNull(pkValueOrDataObject, "parameter 'pkValueOrDataObject' is required");

        if (pkValueOrDataObject instanceof List) {
            @SuppressWarnings("unchecked")
            List<Object> pkValues = (List<Object>) pkValueOrDataObject;
            return this.executeCommand(command, pkValues, option);
        }

        if (pkValueOrDataObject.getClass().isArray()) {
            @SuppressWarnings("unchecked")
            TBeanType[] dataObjects = (TBeanType[]) pkValueOrDataObject;
            this.executeCommand(command, dataObjects, option);
        }

        if (pkValueOrDataObject instanceof Number || pkValueOrDataObject instanceof String) {
            Object pkValue = pkValueOrDataObject;
            return this.getCommandExecutor().execute(this, command, Arrays.asList(pkValue), option);
        }

        Object dataObject = pkValueOrDataObject;
        if (!this.beanType.isAssignableFrom(dataObject.getClass())) {
            throw new IllegalArgumentException("dataObject must be '" + this.beanType.getSimpleName() + "'");
        }

        Object dataObjects = Array.newInstance(this.beanType, 1);
        Array.set(dataObjects, 0, dataObject);

        return this.getCommandExecutor().execute(this, command, (Object[]) dataObjects, option);
    }

    /**
     * 执行传入的命令，传入具体的业务对象，并传入option对象控制命令执行过程内部的一些逻辑
     *
     * @param command
     * @param dataObjects
     * @return
     */
    public final CommandResult executeCommand(Command command, TBeanType[] dataObjects) {
        return this.getCommandExecutor().execute(this, command, dataObjects);
    }

    /**
     * 执行传入的命令，传入具体的业务对象
     *
     * @param command
     * @param dataObjects
     * @param option
     * @return
     */
    public final CommandResult executeCommand(Command command, TBeanType[] dataObjects, CommandExecuteOption option) {
        return this.getCommandExecutor().execute(this, command, dataObjects, option);
    }

    /**
     * 获取当前实体对应的PO类型
     *
     * @return
     */
    public final Class<TBeanType> getBeanType() {
        return beanType;
    }

    private void setBeanType(Class<TBeanType> beanType) {
        this.beanType = beanType;

        IDataObjectMeta objectMeta = BeanMetaManager.getBeanMeta(beanType);

        if (StringUtils.isEmpty(this.getName())) {
            this.setName(this.getClass().getSimpleName());
        }

        if (StringUtils.isEmpty(this.getDisplayName())) {
            this.setDisplayName(objectMeta.getDisplayName());
        }

        IDataField codeField = objectMeta.getCodeField();
        if (codeField != null && StringUtils.isEmpty(this.getCodePropertyName())) {
            this.setCodePropertyName(codeField.getName());
        }

        IDataField nameField = objectMeta.getNameField();
        if (nameField != null && StringUtils.isEmpty(this.getNamePropertyName())) {
            this.setNamePropertyName(nameField.getName());
        }
    }

    /**
     * 获取内置的删除命令
     *
     * @return
     */
    public final DeleteCommand getDeleteCommand() {
        return (DeleteCommand) this.getCommand(DomainConstants.Command.DEFAULT_DELETE_COMMAND_NAME, true);
    }

    @Override
    public final QueryMeta getQueryMeta() {
        return BeanQueryMeta.get(this.beanType);
    }

    /**
     * 获取内置的保存命令
     *
     * @return
     */
    public final SaveCommand getSaveCommand() {
        return (SaveCommand) this.getCommand(DomainConstants.Command.DEFAULT_SAVE_COMMAND_NAME, true);
    }

    /**
     * {@link BeanEntity}提供一个内置的保存命令，该方法即执行这个内置命令
     *
     * @param bean
     * @return
     * @see #getSaveCommand()
     */
    public final CommandResult save(TBeanType bean) {
        return this.save(bean, null);
    }

    /**
     * {@link BeanEntity}提供一个内置的保存命令，该方法即执行这个内置命令
     *
     * @param bean
     * @param option
     * @return
     * @see #getSaveCommand()
     */
    @SuppressWarnings("unchecked")
    public final CommandResult save(TBeanType bean, SaveCommandExecuteOption option) {
        Object beans = Array.newInstance(this.beanType, 1);
        Array.set(beans, 0, bean);

        return this.save((TBeanType[]) beans, option);
    }

    /**
     * {@link BeanEntity}提供一个内置的保存命令，该方法即执行这个内置命令
     *
     * @param beans
     * @return
     * @see #getSaveCommand()
     */
    public final CommandResult save(TBeanType[] beans) {
        return this.executeCommand(this.getSaveCommand(), beans, null);
    }

    /**
     * {@link BeanEntity}提供一个内置的保存命令，该方法即执行这个内置命令，传入option对象控制命令执行过程内部的一些逻辑
     *
     * @param beans
     * @param option
     * @return
     * @see #getSaveCommand()
     */
    public final CommandResult save(TBeanType[] beans, SaveCommandExecuteOption option) {
        return this.executeCommand(this.getSaveCommand(), beans, option);
    }

    /**
     * 使用传入命令关联的验证器对业务对象进行校验
     *
     * @param command
     * @param dataObjects
     * @return
     */
    public final ValidationErrors validate(Command command, TBeanType[] dataObjects) {
        return this.getCommandExecutor().validate(this, command, dataObjects);
    }

    /**
     * 使用传入命令关联的验证器对业务对象进行校验，传入option对象控制校验执行过程内部的一些逻辑
     *
     * @param command
     * @param dataObjects
     * @param option
     * @return
     */
    public final ValidationErrors validate(Command command, TBeanType[] dataObjects, CommandExecuteOption option) {
        return this.getCommandExecutor().validate(this, command, dataObjects, option);
    }

    protected List<Command> createCommands() {
        return Collections.emptyList();
    }

    @Override
    protected IDataObjectMeta createDataObjectMeta() {
        IDataObjectMeta objectMeta = null;

        SubEntityKey subEntityKey = this.getSubEntityKey();
        if (subEntityKey != null) {
            objectMeta = BeanMetaManager.getPartBeanMeta(this.beanType, subEntityKey.getPropertyNameArray());
        } else {
            objectMeta = BeanMetaManager.getBeanMeta(this.beanType);
        }

        this.bindObjectMeta(objectMeta);

        return objectMeta;
    }

    protected DeleteCommand createDeleteCommand() {
        DeleteCommand deleteCommand = new DeleteCommand();
        deleteCommand.setName(DomainConstants.Command.DEFAULT_DELETE_COMMAND_NAME);

        return deleteCommand;
    }

    protected List<Property<?>> createProperties(ChildEntity childEntity) {
        return null;
    }

    protected List<Property<?>> createProperties(Entity entity) {
        return null;
    }

    protected SaveCommand createSaveCommand() {
        SaveCommand saveCommand = new SaveCommand();

        saveCommand.setName(DomainConstants.Command.DEFAULT_SAVE_COMMAND_NAME);
        saveCommand.setEnableIdPropertyUniqueValidaton(true);
        saveCommand.setEnableRequiredPropertyValidator(true);
        saveCommand.setEnableStringValueLengthValidator(true);

        return saveCommand;
    }

    protected List<Validation> createValidations() {
        return Collections.emptyList();
    }

    @SuppressWarnings("unchecked")
    protected Class<TBeanType> findBeanType() {
        Class<?>[] beanTypes = ResolvableType.forType(this.getClass()).as(BeanEntity.class).resolveGenerics();
        return (Class<TBeanType>) beanTypes[0];
    }

    @Override
    protected Entity findReferenceRootEntity(ReferenceProperty referenceProperty) {
        Entity referenceEntity = super.findReferenceRootEntity(referenceProperty);
        if (referenceEntity != null) {
            return referenceEntity;
        }

        Class<?> refBeanType = referenceProperty.getReferenceObjectMeta().getDataObjectClass();

        List<?> entityList = SpringApplicationContext.getInstance().getBeansForAnnatation(EntityMeta.class);
        for (Object entity : entityList) {
            if (entity instanceof BeanEntity<?>) {
                if (((BeanEntity<?>) entity).getBeanType().equals(refBeanType)) {
                    return (Entity) entity;
                }
            }
        }

        return null;
    }

    protected CommandExecutor getCommandExecutor() {
        return commandExecutor;
    }

    protected IDataRepository getRepository() {
        return repository;
    }

    @Override
    protected void onCopy(AbstractEntity copyEntity) {
        super.onCopy(copyEntity);

        @SuppressWarnings("unchecked")
        BeanEntity<TBeanType> copyEntity0 = (BeanEntity<TBeanType>) copyEntity;

        copyEntity0.beanType = this.beanType;
        copyEntity0.commandExecutor = this.commandExecutor;
        copyEntity0.repository = this.repository;
    }

    protected void overrideCommand(Command command) {

    }

    /**
     * 重写Property元素的属性
     *
     * @param childEntity
     * @param property
     */
    protected void overrideProperty(ChildEntity childEntity, Property<?> property) {

    }

    /**
     * 重写Property元素的属性
     *
     * @param entity
     * @param property
     */
    protected void overrideProperty(Entity entity, Property<?> property) {

    }

    protected void registerChildEntityProperties(ChildEntity entity) {

    }

    private void registerCommands() {
        SaveCommand saveCommand = this.createSaveCommand();
        if (saveCommand != null) {
            this.addCommand(saveCommand);
        }

        DeleteCommand deleteCommand = this.createDeleteCommand();
        if (deleteCommand != null) {
            this.addCommand(deleteCommand);
        }
        List<Command> commands = this.annotationResolver.createCommands();
        List<Command> commands0 = this.createCommands();

        if (!CollectionUtils.isEmpty(commands0)) {
            commands.addAll(commands0);
        }

        if (!CollectionUtils.isEmpty(commands)) {
            for (Command command : commands) {
                this.overrideCommand(command);

                this.addCommand(command);
            }
        }
    }

    private void registerProperties() {
        IDataObjectMeta beanMeta = BeanMetaManager.getBeanMeta(this.beanType);
        this.registerProperties(this, beanMeta);
    }

    private void registerProperties(AbstractEntity entity, IDataObjectMeta objectMeta) {
        List<Property<?>> properties = this.annotationResolver.createProperties(entity);
        List<Property<?>> properties0 = null;

        if (entity instanceof Entity) {
            properties0 = this.createProperties((Entity) entity);
        } else if (entity instanceof ChildEntity) {
            properties0 = this.createProperties((ChildEntity) entity);
        }

        if (!CollectionUtils.isEmpty(properties0)) {
            properties.addAll(properties0);
        }

        if (!CollectionUtils.isEmpty(properties)) {
            for (Property<?> property : properties) {
                if (entity instanceof Entity) {
                    this.overrideProperty((Entity) entity, property);
                } else if (entity instanceof ChildEntity) {
                    this.overrideProperty((ChildEntity) entity, property);
                }

                entity.addProperty(property);
            }
        }

        Collection<ICollectionField> collectionFields = objectMeta.getCollectionFields();
        if (CollectionUtils.isEmpty(collectionFields)) {
            return;
        }

        for (ICollectionField collectionField : collectionFields) {
            String childEntityName = collectionField.getName();

            if (entity instanceof ChildEntity) {
                childEntityName = entity.getName() + "." + childEntityName;
            }

            ChildEntity childEntity = new BeanChildEntity();
            childEntity.setName(childEntityName);

            entity.addChildEntity(childEntity);

            this.registerProperties(childEntity, collectionField.getItemObjectMeta());
        }
    }

    private void registerValidations() {
        List<Validation> validations = this.annotationResolver.createValidations();
        List<Validation> validations0 = this.createValidations();

        if (!CollectionUtils.isEmpty(validations0)) {
            validations.addAll(validations0);
        }

        if (!CollectionUtils.isEmpty(validations)) {
            for (Validation validation : validations) {
                this.addValidation(validation);
            }
        }
    }

}
